<?php
namespace app\command;

use app\common\model\system\SqlToRedis;
use think\console\Input;
use think\console\Output;



/**
 * 获取历史火币数据，并进行保存
 * Class HistoryTrade
 * @package app\command
 */
class RuleCalculation
{

    // 设置监控的币种
    const TABLE_MONITOR_LIST = 'monitor_list';

    // 保存规则列表
    const TABLE_MONITOR_RULE_LIST = 'monitor_rule_list';


    // 用户key表
    const TABLE_USER_KEYS = 'user_keys';


    // 保存获取到的数据
    const TABLE_READ = 'history_read_';

    // 设置日志表最后生成时间
    const TABLE_READ_LAST_CREATE = 'history_read_last_createTime';



    // 最后执行api请求时间
    const TABLE_LAST_REQUEST = 'api_last_run_time';

    // 监控订单状态执行中记录表
    const TABLE_TRADE_CALL = 'api_list_run_by_monitor';


    /**
     * 休眠等待时间，单位微妙
     */
    const USLEEP_TIME_LIST = [
        'ERROR_RESTART' => 500000,  //进程失败重启时间

    ];




    private $tableName_history = null;

    private $tableName_symbolToRuleId = null;


    /**
     * 执行记录参数
     * @var int[]
     */
    private $requestRecordConfig = [
        'interval'  => 5, //间隔时间，单位 秒
        'typeList'  => [
            'first' => 1,   //首单
            'cover' => 2,   //补仓
            'clearance' => 3,   //清仓
        ],
        'callApiNum'    => 15,  //开启api最大请求数
    ];

    // 小数点位数
    private $scale = 30;



    /**
     * 保存记录
     */
    const READ_PROCESS_NUM = [
        'master'    => 8,
        'read'      => 3,
        'del'       => 1,
        'calculation'       => 4,  // 用于计算的进程数
        'apiErrWaitTime'    => 60,   //API调用失败等待时间【单位分钟】
    ];

    /**
     * while循环的间隔时间
     * @var int
     */
    private $usleepTimeDefault = 100000;


    /**
     * 订单监控参数设置
     * @var int[]
     */
    private $tradeMonitorConfig = [
        'processMax'    => 5,  //子进程最大数量
        'callWaitTime'  => 20,   //请求等待时间，单位秒
    ];

    public function __construct($tableName_history,$tableName_symbolToRuleId)
    {
        $this->tableName_history = $tableName_history;
        $this->tableName_symbolToRuleId = $tableName_symbolToRuleId;
    }


    /**
     *  * 重写execute
     *  * {@inheritdoc}
     *
     * @param Input $input
     * @param Output $output
     */
    public function execute()
    {

        // 创建内存表
        $this->createSwooleTable();



        // 设置监控配置
        $this->setConfig();

        // 备份配置文件
        $this->configBackup();




        // 监控请求api处理
  //      $this->callApiMaster();

        //监听读取数据，并获取监听列表
        return $this->readAndCalculation();;
    }

    /**
     * 失败重启执行函数
     * @param $function
     */
    private function startProcessAndErrorRestart($function,$isMaster = false) {
        return Swoole::startProcess(function($process) use($function){
            while (true) {
                try {
                    // 执行数据处理
                    call_user_func_array($function,[$process]);
                } catch (\Exception $e) {
                    //       echo $e->getMessage(),':',$e->getLine(),"\n";
                    usleep(self::USLEEP_TIME_LIST['ERROR_RESTART']);
                }
            }
        },true,$isMaster);
    }


    /**
     * 创建进程所需要的内存表（仅线程内操作）
     */
    private function createSwooleTable()
    {
        $sizeUnit = 1024;

        Swoole::getWaitTable();

        /************* 监控表 **************/
        Swoole::table(static::TABLE_MONITOR_LIST,[
            ['begin_time','TYPE_INT',11],
            ['last_id','TYPE_STRING',30],
        ],$sizeUnit);
        /*********** END 监控表 ************/

        /************* 监控规则表 **************/
        Swoole::table(static::TABLE_MONITOR_RULE_LIST,[
            ['coin_id','TYPE_INT',11],
            ['coin_rule_run_id','TYPE_INT',11],
            ['code','TYPE_STRING',11],
            ['user_id','TYPE_INT',11],
            ['first_amount','TYPE_STRING',30],
            ['stop_profit_ratio','TYPE_STRING',10],
            ['stop_profit_callback','TYPE_STRING',10],
            ['is_first_double','TYPE_INT',1],

            ['cover_times','TYPE_INT',5],
            ['cover_times_auto','TYPE_INT',5],
            ['cover_times_manual','TYPE_INT',5],

            ['cover_overlap','TYPE_STRING',10],  //当前次数的补仓幅度
            ['cover_callback','TYPE_STRING',10],    //当前次数的补仓回调

            ['amount','TYPE_STRING',46],
            ['quantity','TYPE_STRING',46],
            ['unit_price','TYPE_STRING',46],
            ['cover_unit_price','TYPE_STRING',46],  //补仓单价【计算跌幅的单价】
            ['stop_unit_price','TYPE_STRING',46],  //止盈单价【计算止盈的单价】
            ['validate','TYPE_STRING',40],
            ['last_buy_err_time','TYPE_INT',11],    // 最后一次买入失败
            ['last_sell_err_time','TYPE_INT',40],   // 最后一次卖出失败


            /************* 以下为每次补仓后刷新为 0 的数据 **************/
            ['overlap_price','TYPE_STRING',46], // 跌幅价
            ['min_price','TYPE_STRING',46], // 最低价
            ['cover_price','TYPE_STRING',46],   // 补仓价【获取最低价时计算】

            ['stop_ratio_price','TYPE_STRING',46],   // 止盈比例价
            ['max_price','TYPE_STRING',46],   // 最高价
            ['stop_price','TYPE_STRING',46],   // 止盈价【获取最高价时计算】

            ['current_price','TYPE_STRING',46],   // 当前价
            /*********** END 以下为每次补仓后刷新为 0 的数据 ************/

            ['last_fetch_time','TYPE_STRING',15],   //最后获取判断时间
            ['last_fetch_id','TYPE_STRING',30],   //最后获取判断的ID
            ['last_run_time','TYPE_INT',11],   //最后本地时间

        ],$sizeUnit * 10,true);
        /*********** END 监控规则表 ************/



        /************* 用户密钥表 **************/
        Swoole::table(static::TABLE_USER_KEYS,[
            ['account_id','TYPE_STRING',12],
            ['access_key','TYPE_STRING',50],
            ['secret_key','TYPE_STRING',50],
        ],$sizeUnit);
        /*********** END 用户密钥表 ************/


        /************* 记录创建表的最后时间 **************/
        Swoole::table(static::TABLE_READ_LAST_CREATE,[
            ['last_create_time','TYPE_STRING',21],
        ],$sizeUnit);
        /*********** END 记录创建表的最后时间 ************/



        /************* 创建最后请求api表记录 **************/
        Swoole::table(static::TABLE_LAST_REQUEST,[
            ['last_run_time','TYPE_INT',11],
            ['ts','TYPE_STRING',15],    // 交易时间，毫秒
            ['trade-id','TYPE_STRING',15],
            ['type','TYPE_STRING',10],  // 1=首单；2=>补仓；3=>清仓
            ['coin_rule_id','TYPE_INT',11],
            ['coin_rule_run_id','TYPE_INT',11],
            ['cover_times','TYPE_INT',4],  // 补仓册数
            ['price_min','TYPE_STRING',40],  // 最低价
            ['price_max','TYPE_STRING',40],  // 最高价
            ['price_current','TYPE_STRING',40],  // 当前价
            ['status','TYPE_INT',1],    // 状态：1=>等待调用；2=>接口请求中；3=>已调用，等待结果；
        ],$sizeUnit * 10,true);
        /************* END 创建最后请求api表记录 **************/

        /************* 创建最后请求api表记录 **************/
        Swoole::table(static::TABLE_TRADE_CALL,[
            ['last_run_time','TYPE_INT',11],
        ],$sizeUnit,true);
        /************* END 创建最后请求api表记录 **************/

    }

    /**
     * 开启配置写入 redis 以及导入配置监控
     * @param string $type  restore 为恢复，其他为备份
     * @return bool
     */
    private function configBackup($type = ''){
        // 设置需要备份的表
        $backupTableNameList = [ static::TABLE_MONITOR_LIST,static::TABLE_MONITOR_RULE_LIST,static::TABLE_USER_KEYS];
        if($type == 'restore') {    // 执行恢复操作
            for($i =0;$i < 5;++$i) {
                try {
                    if(($configList = (new SqlToRedis())->getRuleConfig()) && is_array($configList)) { //获取备份信息
                        foreach($configList as $tableName=>$data) {
                            if(in_array($tableName,$backupTableNameList)) {
                                $table =  Swoole::table($tableName);
                                foreach($data  as $key=>$value) {
                                    $table->set($key,$value);
                                }
                            }
                        }
                        break;
                    }
                    break;
                } catch (\Exception $e) {
                }
                usleep($this->usleepTimeDefault * 2);
            }
            return true;
        }

        // 执行备份操作
        Swoole::startProcess(function() use($backupTableNameList){
            while(true) {
                try {
                    $isBackup = true;   //用于判断是否备份
                    $result = [];
                    foreach($backupTableNameList as $tableName) {
                        $result[$tableName] = [];
                        $table =  Swoole::table($tableName);
                        foreach($table as $key=>$value) {
                            $result[$tableName][$key] = $value;
                        }
                        if(empty($result[$tableName])) {   // 存在空表则不进行备份
                            $isBackup = false;
                        }
                    }
                    if($isBackup) { //对数据进行备份
                        (new SqlToRedis())->saveRuleConfig($result);    //保存备份信息
                    }

                }catch (\Exception $e) {
                }
                usleep(500000);   //每 0.5 s执行一次保存
            }
        });
    }


    /**
     * 设置监控配置
     */
    private function setConfig()
    {

        for($i=0;$i<1;++$i) {    //设置开启线程数
            Swoole::startProcess(function(){
                // 还原配置信息
                $this->configBackup('restore');

                $monitorTable = Swoole::table(static::TABLE_MONITOR_LIST);
                $monitorRuleTable = Swoole::table(static::TABLE_MONITOR_RULE_LIST);
                $userKeysTable = Swoole::table(static::TABLE_USER_KEYS);
                $validateKeyList = [
                    'monitorTable'  => '',
                    'monitorRuleTable'  => '',
                    'userKeysTable'  => '',
                ];

                // 获取验证变化字符串
                $getValidateFunc = function($data){
                    $tmp = json_encode($data);
                    return md5($tmp) . strlen($tmp);
                };

                // 设置api失败等待时间（s）
                $apiErrWaitTime = static::READ_PROCESS_NUM['apiErrWaitTime'] * 60;

                while (true) {
                    $update = false;    //是否需要更新监控表
                    // 获取随时变化配置信息
                    try {   //避免redis读取错误
                        list($monitorList,$monitorRuleList,$userKeys) = $this->getUserKeysAndStrategys();
                    }catch (\Exception $e) {
                        usleep($this->usleepTimeDefault);
                        continue;
                    }

                    /*************** 更新用户表 ******************/
                    $tmpValidate = $getValidateFunc($userKeys);
                    if($validateKeyList['userKeysTable'] != $tmpValidate) {
                        foreach($userKeys as $userId=>$value) {
                            $userKeysTable->set($userId,$value);    //更新用户表
                        }
                        $update = true;
                        $validateKeyList['userKeysTable'] = $tmpValidate;
                    }
                    /************* END 更新用户表 ****************/


                    /*************** 更新监控表 ******************/
                    $tmpValidate = $getValidateFunc($monitorList);
                    if($validateKeyList['monitorTable'] != $tmpValidate) {
                        foreach($monitorList as $fieldName) {
                            // 判断是否获取到数据，不存在则保存
                            if(!$monitorTable->get($fieldName)) {
                                $monitorTable->set($fieldName,[
                                    'begin_time'    => time() - 1,
                                    'last_id'   => '',
                                ]);
                            }
                        }
                        $update = true;
                        $validateKeyList['monitorTable'] = $tmpValidate;
                    }
                    /************* END 更新监控表 ****************/


                    /*************** 更新规则 ******************/
                    $tmpValidate = $getValidateFunc($monitorRuleList);
                    if($validateKeyList['monitorRuleTable'] != $tmpValidate) {
                        foreach($monitorRuleList as $fieldName=>$value) {
                            if(($record = $monitorRuleTable->get($fieldName)) && $record['validate'] == $value['validate']) {
                                continue;
                            }

                            $nowTime = time();
                            // 获取止盈涨幅
                            $getStopRatioPriceFunc = function($stopUnitPrice,$stopProfitRatio){
                                return   bcadd($stopUnitPrice,bcmul($stopUnitPrice,bcdiv($stopProfitRatio,100,$this->scale),$this->scale),$this->scale);
                            };

                            // 获取补仓跌幅
                            $getOverlapPriceFunc = function ($coverUnitPrice,$coverOverlap) {
                                return  bcsub($coverUnitPrice,bcmul($coverUnitPrice,bcdiv($coverOverlap,100,$this->scale),$this->scale),$this->scale);
                            };
                            if($record) {   //存在记录

                                $newData = [];

                                /******* 判断跌幅计算 *********/
                                if($nowTime - $value['last_buy_err_time'] < $apiErrWaitTime) {
                                    $newData = array_merge($newData,[
                                        'min_price' => 0,
                                        'overlap_price' => 0,
                                        'cover_price'   => 0,
                                    ]);
                                } else if(
                                    bccomp($record['last_buy_err_time'],$value['last_buy_err_time'],$this->scale) !== 0 ||    //失败时间
                                    bccomp($record['cover_unit_price'],$value['cover_unit_price'],$this->scale) !== 0 ||    //补仓单价
                                    bccomp($record['cover_overlap'],$value['cover_overlap'],$this->scale) !== 0 ||  // 补仓跌幅
                                    bccomp($record['cover_callback'],$value['cover_callback'],$this->scale) !== 0 ||  // 补仓回调
                                    bccomp($record['cover_times_auto'],$value['cover_times_auto'],$this->scale) !== 0  // 补仓次数
                                ) {
                                    $newData = array_merge($newData,[
                                        'min_price' => 0,
                                        'overlap_price' => $getOverlapPriceFunc($value['cover_unit_price'],$value['cover_overlap']),
                                        'cover_price'   => 0,
                                    ]);
                                }
                                /***** END 判断跌幅计算 *******/


                                /******* 判断止盈计算 *********/
                                if($nowTime - $value['last_sell_err_time'] < $apiErrWaitTime) {
                                    $newData = array_merge($newData,[
                                        'max_price' => 0,
                                        'stop_ratio_price'   => 0,
                                        'stop_price'   => 0,
                                    ]);
                                } else if(
                                    bccomp($record['last_sell_err_time'],$value['last_sell_err_time'],$this->scale) !== 0 ||    //失败时间
                                    bccomp($record['stop_unit_price'],$value['stop_unit_price'],$this->scale) !== 0 ||    //止盈单价
                                    bccomp($record['stop_profit_ratio'],$value['stop_profit_ratio'],$this->scale) !== 0 ||  // 止盈涨幅
                                    bccomp($record['stop_profit_callback'],$value['stop_profit_callback'],$this->scale) !== 0  // 止盈回调

                                ) {
                                    $newData = array_merge($newData,[
                                        'max_price' => 0,
                                        'stop_ratio_price' => $getStopRatioPriceFunc($value['stop_unit_price'],$value['stop_profit_ratio']),
                                        'stop_price'   => 0,
                                    ]);
                                }
                                /***** END 判断止盈计算 *******/

                                // 合并为最新数据
                                $value = array_merge($record,$value,$newData);
                            } else {    //不存在记录
                                // 需要的默认值
                                $value = array_merge($value,[
                                    'min_price' => 0,
                                    'overlap_price' => 0,
                                    'cover_price'   => 0,

                                    'max_price' => 0,
                                    'stop_ratio_price'   => 0,
                                    'stop_price'   => 0,

                                    'last_fetch_time' => time(),
                                    'last_fetch_id' => '',
                                    'last_run_time' => time(),
                                ]);
                                if($nowTime - $value['last_buy_err_time'] > $apiErrWaitTime && bccomp($value['cover_unit_price'],0,$this->scale) == 1) {    //计算补仓跌幅
                                    $value['overlap_price'] = $getOverlapPriceFunc($value['cover_unit_price'],$value['cover_overlap']);
                                }

                                if($nowTime - $value['last_sell_err_time'] > $apiErrWaitTime && bccomp($value['stop_unit_price'],0,$this->scale) == 1) {    //止盈涨幅计算
                                    $value['stop_ratio_price'] = $getStopRatioPriceFunc($value['stop_unit_price'],$value['stop_profit_ratio']);
                                }
                            }


                            // 保存规则结果
                            $monitorRuleTable->set($fieldName,$value);

                            /******************* 规则更新后直接刷新判断首单 ************************/
                            if(bccomp($value['amount'],0,10) == 0 && $nowTime - $value['last_sell_err_time'] > $apiErrWaitTime) {
                                try {
                                    $this->checkAndInsertRequestRecord($fieldName,$this->requestRecordConfig['typeList']['first'],array_merge($value,['current_price'=>0]),0,0);
                                }catch (\Exception $e) {
                                }
                            }
                            /***************** END 规则更新后直接刷新判断首单 **********************/

                        }
                        $update = true;
                        $validateKeyList['monitorRuleTable'] = $tmpValidate;
                    }
                    /************* END 更新规则 ****************/

                    if($update) {   //更新数据，删除旧监控
                        foreach($monitorTable as $symbol=>$value) {
                            if(!in_array($symbol,$monitorList)) {
                                $monitorTable->del($symbol); //删除监控
                            }
                        }

                        foreach($userKeysTable as $userId=>$value) {
                            if(!array_key_exists($userId,$userKeys)) {
                                $userKeysTable->del($userId); //删除用户信息
                            }
                        }

                        foreach($monitorRuleTable as $fieldName=>$value) {
                            if(!array_key_exists($fieldName,$monitorRuleList)) {
                                $monitorRuleTable->del($fieldName); //删除监控规则
                            }
                        }
                    }
                    usleep($this->usleepTimeDefault);
                }
            });
        }
    }


    /**
     * 读取并进行计算
     */
    private function readAndCalculation(){
        $calculationList = [];
        for($i=0;$i<static::READ_PROCESS_NUM['master'];++$i) {    //开启分配线程

            /*********************** 开启执行计算的监控进程 *********************************/
            $calculationProcessList = [];
            for($j=0;$j<static::READ_PROCESS_NUM['calculation'];$j++) {
                $calculationProcessList[] = $this->startProcessAndErrorRestart(function($process){
                    if($value = json_decode($process->read(),true)) {
                        list($symbol,$ruleIdList,$tableValue) = $value;
                        $this->calculation($symbol,$ruleIdList,$tableValue);
                    }
                });
            }
            /********************* END 开启执行计算的监控进程 *******************************/


            // 开启获取数据进程
            $calculationList[] = $this->startProcessAndErrorRestart(function($process) use($calculationProcessList){
                // 添加监控进程
                Swoole::monitorProcess('计算管理');
                $symbolList = [];
                while(true) {
                    try {
                        if(!$value = json_decode($process->read(),true)) {
                            continue;
                        }

                        try {
                            $tmp = Swoole::table($this->tableName_symbolToRuleId)->get($value['symbol']);
                            $symbolList[$value['symbol']] = empty($tmp)?[]:explode('|',$tmp['ruleIdList']);
                        } catch (\Exception $e) {
                            HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['ERROR'],$e);
                        }

                        if(!$ruleIdList = $symbolList[$value['symbol']]) {  //不存在规则跳出
                            continue;
                        }
                        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['DISTRIBUTION'], $value['symbol'],$value['ts'],$value['trade-id']);
                        $calculationProcessList[array_rand($calculationProcessList)]->write(json_encode([$value['symbol'],$ruleIdList,$value]));

                    }catch (\Exception $e) {
                        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['ERROR'],$e);
                        usleep($this->usleepTimeDefault);
                    }
                }
            });
            continue;
            // 开启获取redis数据信息处理
            $calculationList[] = Swoole::startProcess(function($process){
                // 添加监控进程
                Swoole::monitorProcess('计算管理');

                $pid = $process->pid;
                $symbolToRuleIdTable = Swoole::table($this->tableName_symbolToRuleId);
                $symbolList = [];
                while(true) {
                    try {
                        if(!$value = json_decode($process->read(),true)) {
                            continue;
                        }

                        try {
                            $tmp = $symbolToRuleIdTable->get($value['symbol']);
                            $symbolList[$value['symbol']] = empty($tmp)?[]:explode('|',$tmp['ruleIdList']);
                        } catch (\Exception $e) {
                            HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['ERROR'],$e);
                        }

                        if(!$ruleIdList = $symbolList[$value['symbol']]) {  //不存在规则跳出
                            continue;
                        }

                        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['DISTRIBUTION'], $value['symbol'],$value['ts'],$value['trade-id']);

                        // 开启等待线程，并执行处理
                        Swoole::startWaitProcess(function($value){
                            list($symbol,$ruleIdList,$tableValue) = $value;
                            return $this->calculation($symbol,$ruleIdList,$tableValue);
                        },[$value['symbol'],$ruleIdList,$value],'readAndCalculation_' . $pid,static::READ_PROCESS_NUM['calculation']);

                        Swoole::closeWaitBySub();
                    }catch (\Exception $e) {
                        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['ERROR'],$e);
                        usleep($this->usleepTimeDefault);
                    }
                }
            },true,true);
        }
        // 返回监听读取列表
        return $calculationList;
    }


    /**
     * 计算规则，判断需要处理的规则，并提交请求
     * @param $ruleList 规则列表
     * @param $lastCreateTable 统计创建时间表
     * @param $tableRecordName  表记录的字段
     * @param $lastTime 最后处理时间
     */
    private function calculation($symbol,$ruleIdList,$tableValue) {
        $monitorRuleTable = Swoole::table(static::TABLE_MONITOR_RULE_LIST);
        $key = HistoryTrade::getAutoId(static::TABLE_READ . $symbol);
        $value = $tableValue;
        // 记录
        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['CALCULATION_BEFOR'], $symbol,$value['ts'],$value['trade-id']);
        $historydetails = $value;
        foreach($ruleIdList as $ruleId) {
            try {
                /************************* 计算规则 *********************/
                if(!($ruleDetails = $monitorRuleTable->get($ruleId))) {
                    HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['ERROR'],$ruleId );
                    continue;
                }

                //计算请求接口
                $ruleDetails = $this->calculationCallApi($ruleId,$ruleDetails,$historydetails['price'],$key,$historydetails['ts'],$historydetails['trade-id']);

                $monitorRuleTable->lockFunc(function($monitorRuleTable) use($ruleId,$ruleDetails){
                    if(($tmpDetails = $monitorRuleTable->get($ruleId))) {
                        $ruleDetails['last_run_time'] = time();
                        // 修改详情信息
                        $monitorRuleTable->set($ruleId,$ruleDetails);
                    }
                });

                /*********************** END 计算规则 *******************/
            }catch (\Exception $e) {
                HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['ERROR'],$e);
            }

        }

        return true;
    }


    /**
     * 计算是否调用API
     * @param $ruleId 规则ID
     * @param $ruleDetails 规则详情
     * @param $price    当前价格
     */
    private function calculationCallApi($ruleId,$ruleDetails,$price,$lastFetchId,$lastFetchTime,$tradeId = 0) {

        //记录日志
        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['CALCULATION'],$ruleDetails['code'],$lastFetchTime,$tradeId,$ruleId,$ruleDetails['coin_rule_run_id']);

        $ruleDetails['last_fetch_id'] = $lastFetchId;
        $ruleDetails['last_fetch_time'] = $lastFetchTime;
        $ruleDetails['current_price'] = $this->convert_scientific_number_to_normal($price); // 当前价钱
        if(bccomp($ruleDetails['unit_price'],0,$this->scale) != 1) {
            /********* 添加首次购买操作 **********/
            $this->checkAndInsertRequestRecord($ruleId,$this->requestRecordConfig['typeList']['first'],$ruleDetails,$lastFetchTime,$tradeId);

            return $ruleDetails;
        }
/*
        if($ruleDetails['cover_price'] > 0)
            echo '当前价格：' . sprintf('当前价钱：%015f 最低价：%015f 补仓价：%015f 回调：%06f',$ruleDetails['current_price'],$ruleDetails['min_price'],$ruleDetails['cover_price'],$ruleDetails['cover_callback']),"\n";
        if($ruleDetails['stop_price'] > 0)
            echo '当前价格：' . sprintf('当前价钱：%015f 最高价：%015f 止盈价：%015f 回调：%06f',$ruleDetails['current_price'],$ruleDetails['max_price'],$ruleDetails['stop_price'],$ruleDetails['stop_profit_callback']),"\n";
        if($ruleDetails['cover_price'] > 0 || $ruleDetails['stop_price'] > 0)
            echo "\n";*/

        $nowTime = time();
        $apiErrWaitTime = static::READ_PROCESS_NUM['apiErrWaitTime'] * 60;

        // 判断最低价
        if($nowTime - $ruleDetails['last_buy_err_time'] > $apiErrWaitTime && (bccomp($ruleDetails['min_price'],0,$this->scale)==0 || bccomp($ruleDetails['min_price'],$ruleDetails['current_price'],$this->scale) == 1)) {  // 需要修改最低价
            $ruleDetails['min_price'] = $ruleDetails['current_price'];
            if(bccomp($ruleDetails['min_price'],$ruleDetails['overlap_price'],$this->scale) != 1) { // 最低价不大于设定值，需要设置回调
                $ruleDetails['cover_price'] = bcadd($ruleDetails['min_price'],bcmul($ruleDetails['min_price'],bcdiv($ruleDetails['cover_callback'],100,8),$this->scale),$this->scale);   // 计算回调价格
            }
        }

        // 计算最高价
        if($nowTime - $ruleDetails['last_sell_err_time'] > $apiErrWaitTime && (bccomp($ruleDetails['max_price'],0,$this->scale)==0  || bccomp($ruleDetails['max_price'],$ruleDetails['current_price'],$this->scale) == -1)) {
            $ruleDetails['max_price'] = $ruleDetails['current_price'];  //重新设置最高价
            if(bccomp($ruleDetails['max_price'],$ruleDetails['stop_ratio_price'],$this->scale) != -1) { // 设置止盈
                $ruleDetails['stop_price'] = bcsub($ruleDetails['max_price'],bcmul($ruleDetails['max_price'],bcdiv($ruleDetails['stop_profit_callback'],100,8),$this->scale),$this->scale);
            }
        }


        if($ruleDetails['cover_overlap'] > 0 && bccomp($ruleDetails['cover_price'],0,$this->scale) == 1 && bccomp($ruleDetails['cover_price'],$ruleDetails['current_price'],$this->scale) != 1) {
            // 存在回调补仓价，并且回调补仓价小于等于当前价，进行补仓
            $this->checkAndInsertRequestRecord($ruleId,$this->requestRecordConfig['typeList']['cover'],$ruleDetails,$lastFetchTime,$tradeId);

        } else if(bccomp($ruleDetails['stop_price'],0,$this->scale) == 1 && bccomp($ruleDetails['stop_price'],$ruleDetails['current_price'],$this->scale) != -1) {
            // 存在止盈价，并止盈价大于或等于当前价，进行止盈处理
            $this->checkAndInsertRequestRecord($ruleId,$this->requestRecordConfig['typeList']['clearance'],$ruleDetails,$lastFetchTime,$tradeId);
        }

        return $ruleDetails;
    }




    /**
     * 将科学计数法的数字转换为正常的数字
     * 为了将数字处理完美一些，使用部分正则是可以接受的
     * @author loveyu
     * @param string $number
     * @return string
     */
    private function convert_scientific_number_to_normal($number)
    {
        if(stripos($number, 'e') === false) {
            //判断是否为科学计数法
            return $number;
        }

        if(!preg_match(
            "/^([\\d.]+)[eE]([\\d\\-\\+]+)$/",
            str_replace(array(" ", ","), "", trim($number)), $matches)
        ) {
            //提取科学计数法中有效的数据，无法处理则直接返回
            return $number;
        }

        //对数字前后的0和点进行处理，防止数据干扰，实际上正确的科学计数法没有这个问题
        $data = preg_replace(array("/^[0]+/"), "", rtrim($matches[1], "0."));
        $length = (int)$matches[2];
        if($data[0] == ".") {
            //由于最前面的0可能被替换掉了，这里是小数要将0补齐
            $data = "0{$data}";
        }

        //这里有一种特殊可能，无需处理
        if($length == 0) {
            return $data;
        }

        //记住当前小数点的位置，用于判断左右移动
        $dot_position = strpos($data, ".");
        if($dot_position === false) {
            $dot_position = strlen($data);
        }

        //正式数据处理中，是不需要点号的，最后输出时会添加上去
        $data = str_replace(".", "", $data);


        if($length > 0) {
            //如果科学计数长度大于0

            //获取要添加0的个数，并在数据后面补充
            $repeat_length = $length - (strlen($data) - $dot_position);
            if($repeat_length > 0) {
                $data .= str_repeat('0', $repeat_length);
            }

            //小数点向后移n位
            $dot_position += $length;
            $data = ltrim(substr($data, 0, $dot_position), "0").".".substr($data, $dot_position);

        } elseif($length < 0) {
            //当前是一个负数

            //获取要重复的0的个数
            $repeat_length = abs($length) - $dot_position;
            if($repeat_length > 0) {
                //这里的值可能是小于0的数，由于小数点过长
                $data = str_repeat('0', $repeat_length).$data;
            }

            $dot_position += $length;//此处length为负数，直接操作
            if($dot_position < 1) {
                //补充数据处理，如果当前位置小于0则表示无需处理，直接补小数点即可
                $data = ".{$data}";
            } else {
                $data = substr($data, 0, $dot_position).".".substr($data, $dot_position);
            }
        }
        if($data[0] == ".") {
            //数据补0
            $data = "0{$data}";
        }
        return trim($data, ".");
    }


    /**
     * 判断规则是否需要进行接口请求
     * @param $ruleId 规则ID
     * @param $type
     * @param $ruleDetails
     */
    private function checkAndInsertRequestRecord($ruleId,$type,$ruleDetails,$recordTs = 0,$tradeId=0) {
        $requestTable = Swoole::table(static::TABLE_LAST_REQUEST);
        $key = implode('__',[$ruleId]);
        $interval = $this->requestRecordConfig['interval'];
        if(($requestDetails = $requestTable->get($key)) &&
            $requestDetails['type'] == $type &&
            $requestDetails['coin_rule_run_id'] == $ruleDetails['coin_rule_run_id'] &&
            $requestDetails['cover_times'] == $ruleDetails['cover_times_auto'] &&
            array_key_exists('last_run_time',$requestDetails) && time() - $requestDetails['last_run_time'] < $interval) {
            return '';  //忽视操作，去重
        }

        $requestNew = [
            'last_run_time' => time(),
            'ts'    => $recordTs,
            'trade-id'  => $tradeId,
            'type'  =>  $type,
            'coin_rule_id'  => $ruleId,
            'coin_rule_run_id'  => $ruleDetails['coin_rule_run_id'],
            'cover_times'   => $ruleDetails['cover_times_auto'],
            'price_min' => $ruleDetails['min_price'],
            'price_max' => $ruleDetails['max_price'],
            'price_current' => $ruleDetails['current_price'],
            'status'    => 1,
        ];

        // 启用锁处理，并且判断添加数据
        $requestTable->lockFunc(function($table) use($key,$requestNew,$interval,$type,$ruleDetails){
            if(($requestDetails = $table->get($key)) &&
                $requestDetails['type'] == $type &&
                $requestDetails['coin_rule_run_id'] == $ruleDetails['coin_rule_run_id'] &&
                $requestDetails['cover_times'] == $ruleDetails['cover_times_auto'] &&
             array_key_exists('last_run_time',$requestDetails) && time() - $requestDetails['last_run_time'] < $interval) {
                return '';  //忽视操作，去重
            }
            $table->set($key,$requestNew);
            $requestNew = $table->get($key);
            ksort($requestNew);
            TradeServer::send([
                'portIndex' => HistoryTrade::$portIndex,
                'key'   => $key,
                'validate'  => md5(json_encode($requestNew)),
                'value' => $requestNew,
            ]);
        });
        HistoryTrade::takeTime(HistoryTrade::TAKE_TIME_TYPE_LIST['CALL_APPLY'],$ruleDetails['code'],$recordTs,$tradeId,$ruleId,$ruleDetails['coin_rule_run_id']);
        return true;
    }




    /**
     * 获取用户的key列表 及 货币被监控的用户列表
     */
    private function getUserKeysAndStrategys()
    {
        $apiErrWaitTime = static::READ_PROCESS_NUM['apiErrWaitTime'] * 60;
        $monitorList = $monitorRuleList = $userKeys =[];
        $result = HistoryTrade::getUserRule(HistoryTrade::USER_RULE_CONFIG['TYPE_LIST']['RULE_CALCULATION']);
      //  $result = (new SqlToRedis())->getUserRule();
        foreach($result as $ruleDetails) {
            $nowTime = time();
            $monitorList[] = $ruleDetails['code'];
            $rule = [];
            $ruleFields = ['coin_id','coin_rule_run_id','code','user_id','first_amount','stop_profit_ratio','stop_profit_callback','is_first_double','coin_rule_run_id','cover_times','cover_times_auto',
                'cover_times_manual','amount','quantity','unit_price','cover_unit_price','stop_unit_price','last_buy_err_time','last_sell_err_time'];
            foreach($ruleFields as $field) {
                $rule[$field] = is_null($ruleDetails[$field])?0:$ruleDetails[$field];
            }
            if($nowTime - $rule['last_buy_err_time'] > $apiErrWaitTime) {
                $rule['last_buy_err_time'] = 1;
            }

            if($nowTime - $rule['last_sell_err_time'] > $apiErrWaitTime) {
                $rule['last_sell_err_time'] = 1;
            }
            $rule = array_merge($rule,[
                'cover_overlap'     => 0,
                'cover_callback'    => 0,
            ]);
            if($rule['unit_price'] > 0) {
                foreach($ruleDetails['coverList'] as $coverDetails) {
                    if($coverDetails['frequency'] == $rule['cover_times_auto'] + 1) {
                        $rule = array_merge($rule,[
                            'cover_overlap'  => $coverDetails['overlap'],
                            'cover_callback'    => $coverDetails['callback'],
                        ]);
                        break;
                    }
                }
            }

            $rule['validate'] = json_encode($rule);
            $rule['validate'] = md5($rule['validate']) . strlen($rule['validate']);
            $monitorRuleList[$ruleDetails['id']] = $rule;


            // 用户数据保存
            $userKeys[$ruleDetails['user_id']] = [
                'account_id'    => $ruleDetails['account_id'],
                'access_key'    => $ruleDetails['access_key'],
                'secret_key'    => $ruleDetails['secret_key'],
            ];
        }
        return [$monitorList,$monitorRuleList,$userKeys];
    }
}